package ar.utn.frba.ia.IA_TP1;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.imageio.ImageIO;

import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.DefaultFitnessEvaluator;
import org.jgap.Gene;
import org.jgap.Genotype;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.event.EventManager;
import org.jgap.impl.BestChromosomesSelector;
import org.jgap.impl.ChromosomePool;
import org.jgap.impl.CompositeGene;
import org.jgap.impl.CrossoverOperator;
import org.jgap.impl.GABreeder;
import org.jgap.impl.IntegerGene;
import org.jgap.impl.MutationOperator;
import org.jgap.impl.StockRandomGenerator;
import org.jgap.impl.ThresholdSelector;
import org.jgap.impl.TournamentSelector;
import org.jgap.impl.WeightedRouletteSelector;

import ar.utn.frba.ia.IA_TP1.genetica.MapFitness;
import ar.utn.frba.ia.IA_TP1.mapas.Africa;
import ar.utn.frba.ia.IA_TP1.mapas.AmericaDelNorte;
import ar.utn.frba.ia.IA_TP1.mapas.AmericaDelSur;
import ar.utn.frba.ia.IA_TP1.mapas.EuroAsia;
import ar.utn.frba.ia.IA_TP1.mapas.Mapa;
import ar.utn.frba.ia.IA_TP1.mapas.Mapa.Colores;
import ar.utn.frba.ia.IA_TP1.mapas.Pais;

public class App
{
	private static int iteraciones = 0;
	private static int poblacion = 0;
	private static String soName;
	private static List<Mapa> mapas;
	private static ConfigUsed configUsed;
	private static String dirResultados="ejecucion";

	public static void main( String[] args ) throws InvalidConfigurationException, IOException
	{
		Boolean continuar = null;

		if(args.length < 2){
			System.out.println("Error ingreso de parametros");
			System.out.println("El orden es: [nroIteraciones] [cantidadDePoblacionInicial]");
			return;
		}

		try{
			iteraciones = Integer.parseInt(args[0].trim());
			poblacion = Integer.parseInt(args[1].trim());

		}catch(NumberFormatException e){
			System.out.println("Error ingreso de parametros");
			System.out.println("El orden es: [nroIteraciones] [cantidadDePoblacionInicial]");
			return;
		}

		//Sólo una configuración por corrida (keep it simple)
		do{
			configUsed = new ConfigUsed();
			configUsed.setCantidadIteraciones(iteraciones);
			configUsed.setCantidadPoblacion(poblacion);

			Configuration.reset();
			Configuration configuration = new Configuration();

			configuration.setBreeder(new GABreeder());
			configuration.setRandomGenerator(new StockRandomGenerator());
			configuration.setEventManager(new EventManager());
			configuration.setMinimumPopSizePercent(0);
			configuration.setSelectFromPrevGen(1.0d);
			configuration.setKeepPopulationSizeConstant(false);
			configuration.setFitnessEvaluator(new DefaultFitnessEvaluator());
			configuration.setChromosomePool(new ChromosomePool());
			
			configuration.setPopulationSize(poblacion);

			//Funcion de aptitud
			MapFitness funcionFitness = new MapFitness();
			configuration.setFitnessFunction(funcionFitness);

			addMapas();
			funcionFitness.addMapas(mapas);

			agregarSelectores(configuration);

			agregarOperadores(configuration);

			agregarMutacion(configuration);

			System.out.println("Calculando...");


			// Hago un Chromosoma
			CompositeGene americaDelSurCGene = new CompositeGene(configuration);
			for(int nroGenes = 0; nroGenes < mapas.get(MAPAS.AmS.getNumero()).getCantidadPaises(); nroGenes++)
				americaDelSurCGene.addGene(new IntegerGene(configuration,0,3));

			CompositeGene americaDelNorteCGene = new CompositeGene(configuration);
			for(int nroGenes = 0; nroGenes < mapas.get(MAPAS.AmN.getNumero()).getCantidadPaises(); nroGenes++)
				americaDelNorteCGene.addGene(new IntegerGene(configuration,0,3));

			CompositeGene euroAsiaCGene = new CompositeGene(configuration);
			for(int nroGenes = 0; nroGenes < mapas.get(MAPAS.EA.getNumero()).getCantidadPaises(); nroGenes++)
				euroAsiaCGene.addGene(new IntegerGene(configuration,0,3));

			CompositeGene africaCGene = new CompositeGene(configuration);
			for(int nroGenes = 0; nroGenes < mapas.get(MAPAS.Af.getNumero()).getCantidadPaises(); nroGenes++)
				africaCGene.addGene(new IntegerGene(configuration,0,3));

			Chromosome chromosome = new Chromosome(configuration,
					new Gene[]{americaDelSurCGene,americaDelNorteCGene,euroAsiaCGene,africaCGene});

			//Cromosoma a clonar
			configuration.setSampleChromosome(chromosome);

			//It's makes sense as the spell damage in D3 does ;)...
			Genotype population = Genotype.randomInitialGenotype(configuration);

			//Evolucion
			List<String> fitnessHistorico = new ArrayList<String>();
			List<Integer> cantidadPoblacion = new ArrayList<Integer>();
			for(int i=0;i<iteraciones;i++){
				population.evolve();
				fitnessHistorico.add(Double.toString(population.getFittestChromosome().getFitnessValue()));
				cantidadPoblacion.add(population.getPopulation().size());
			}

			String dirResultadosTemp = dirResultados + new Date().getTime();

			generaJsPaises(population, dirResultadosTemp);

			imprimeHistoricoFitness(fitnessHistorico, dirResultadosTemp);

			imprimeHistoricoPoblacion(cantidadPoblacion,dirResultadosTemp);
			
			//Imprimo config
			generaConfig(dirResultadosTemp);
			
			//Copio los mapas html a la carpeta ejecucion
			copioMapaHtml(dirResultadosTemp);

			//Copio la imagen del mapa
			copiarMapas(dirResultadosTemp);

			System.out.println("Fin.");

			continuar=null;
			BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
			while(continuar==null){
				System.out.println("Continuar?(s/n)");
				String read = bufferRead.readLine();
				if(read.equals("s") || read.equals("n")){
					continuar = read.equals("s")? new Boolean(true):new Boolean(false);
				}else{
				}
			}

		}while(continuar);

	}

	private static void copiarMapas(String dirResultados) throws IOException {
		File mapaOrigenImageFile = new File("tegMapa.jpg");

		BufferedImage inputImage=ImageIO.read(mapaOrigenImageFile);
		ImageIO.write(inputImage,"jpg",new File(dirResultados+"/tegMapa.jpg"));
	}

	private static void generaConfig(String dirResultados)
			throws FileNotFoundException, IOException {
		File configCorrida = new File(dirResultados + "/configUsada.txt");
		FileOutputStream configSteam = null;
		configSteam = new FileOutputStream(configCorrida);

		configSteam.write(configUsed.toString().getBytes());

		configSteam.flush();
		configSteam.close();
	}
	

	@SuppressWarnings("resource")
	private static void copioMapaHtml(String dirResultados)
			throws FileNotFoundException, IOException {
		FileChannel source = null;
		FileChannel destination = null;
		//Copiar recursos a un nuevo directorio
		for(int i = 1 ; i <= 3; i++){
			File mapaOrigenFile = new File("Mapas"+i+".html");
			source = new FileInputStream(mapaOrigenFile).getChannel();
			destination = new FileOutputStream(new File(dirResultados+"/MapasResultadoNro"+i+".html")).getChannel();
			destination.transferFrom(source, 0, source.size());

			source.close();
			destination.close();
		}
	}

	@SuppressWarnings("unchecked")
	private static void generaJsPaises(Genotype population, String dirResultados)
			throws FileNotFoundException, IOException {
		List<IChromosome> winningChromosome = population.getFittestChromosomes(3);
		Iterator<IChromosome> itWinningChromosomes = winningChromosome.iterator();

		int nroCromosoma = 0; 

		File resultDirectory = new File(dirResultados);
		resultDirectory.mkdir();

		while(itWinningChromosomes.hasNext()){
			String jsString = "function init() { ";
			File jscript = new File(dirResultados+"/cargarPaises"+(nroCromosoma++)+".js");
			FileOutputStream jscriptOutput = new FileOutputStream(jscript);

			IChromosome cromosomaGanador = itWinningChromosomes.next();
			Gene[] genCGanador = cromosomaGanador.getGenes();
			configUsed.addFitnessSolucion(cromosomaGanador.getFitnessValue());

			Iterator<Mapa> itMapa = mapas.iterator();
			int nroMapa = 0;
			while(itMapa.hasNext()){

				List<Pais> paises = itMapa.next().getPaises();
				for(int i=0;i<paises.size();i++){
					Pais paisAct = paises.get(i);
					paisAct.color = Colores.values()[(Integer)((CompositeGene)genCGanador[nroMapa]).getGenes().get(i).getAllele()];
					jsString += "\n\t" + paisAct.generateJavascript();
				}


				nroMapa++;
			}

			jsString += "\n}";
			jscriptOutput.write(jsString.getBytes());

			jscriptOutput.flush();
			jscriptOutput.close();
		}
	}

	private static void imprimeHistoricoPoblacion(
			List<Integer> cantidadPoblacion, String dirResultados) throws IOException {
		File poblacionHistoricoF = new File(dirResultados + "/poblacionHistorico.xls");
		FileOutputStream poblacionHistoricoOps = null;
		poblacionHistoricoOps = new FileOutputStream(poblacionHistoricoF);
		Iterator<Integer> iTfitness = cantidadPoblacion.iterator();
		while(iTfitness.hasNext()){
			poblacionHistoricoOps.write((iTfitness.next().toString()+";").getBytes());

		}
		poblacionHistoricoOps.flush();
		poblacionHistoricoOps.close();

	}

	private static void imprimeHistoricoFitness(List<String> fitnessHistorico,
			String dirResultados) throws FileNotFoundException, IOException {
		File fitnessHistoricoF = new File(dirResultados + "/fitnessHistorico.xls");
		FileOutputStream fitnessHistoricoOps = null;
		fitnessHistoricoOps = new FileOutputStream(fitnessHistoricoF);
		Iterator<String> iTfitness = fitnessHistorico.iterator();
		while(iTfitness.hasNext()){
			fitnessHistoricoOps.write((iTfitness.next()+";").getBytes());

		}
		fitnessHistoricoOps.flush();
		fitnessHistoricoOps.close();
	}

	@SuppressWarnings("unused")
	private static void limpiarConsola() throws IOException {
		soName = System.getProperty("os.name");
		if(soName.equals("Linux")){
			Runtime.getRuntime().exec("clear");
		}else{
			Runtime.getRuntime().exec("cls");
		}
	}

	private static void addMapas() {
		mapas = new ArrayList<Mapa>();
		mapas.add(MAPAS.AmS.getNumero(),new AmericaDelSur());
		mapas.add(MAPAS.AmN.getNumero(),new AmericaDelNorte());
		mapas.add(MAPAS.EA.getNumero(), new EuroAsia());
		mapas.add(MAPAS.Af.getNumero(), new Africa());
	}

	private static void agregarMutacion(Configuration configuration) throws IOException, InvalidConfigurationException {
		BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
		Boolean allowMutation=null;
		while(allowMutation==null){
			System.out.println("Permitir mutacion(s/n)");
			String read = bufferRead.readLine();
			if(read.equals("s") || read.equals("n")){
				allowMutation = read.equals("s")? new Boolean(true):new Boolean(false);
			}else{
			}
		}

		if(allowMutation){
			Integer ratio = null;
			boolean formatoCorrecto = false;
			while(!formatoCorrecto){
				try{
					System.out.println("Ratio de mutacion");
					ratio = Integer.parseInt(bufferRead.readLine());
					if(ratio > 0){
						formatoCorrecto = true;						
					}else{
						System.out.println("Debe ser un numero positivo");	
					}
				}catch(NumberFormatException e){
					System.out.println("Debe ser un numero");
				}
			}
			configuration.addGeneticOperator(new MutationOperator(configuration, ratio));
			configUsed.addOperador("Mutacion con ratio: "+ratio);
		}

	}

	private static void agregarOperadores(Configuration configuration) throws InvalidConfigurationException, IOException {
		String repeat = "s";
		Double ratio = 0.0;
		Boolean allowFullCrossOver = null;
		Boolean xOverNewAge = null;
		BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));

		do{
			//			limpiarConsola();

			System.out.println("<Agregar Operadores>");
			System.out.println("Porcentaje de poblacion a cruzar");

			boolean formatoCorrecto = false;
			while(!formatoCorrecto){
				try{
					ratio = Double.parseDouble(bufferRead.readLine());
					if(ratio >0 && ratio <= 1){
						formatoCorrecto = true;						
					}else{
						System.out.println("Debe ser un numero decimal menor o igual a 1");	
					}
				}catch(NumberFormatException e){
					System.out.println("Debe ser un numero");
				}
			}

			allowFullCrossOver=null;
			while(allowFullCrossOver==null){
				System.out.println("Full cross over? Antes y despues del punto de corte(s/n).");
				String read = bufferRead.readLine();
				if(read.equals("s") || read.equals("n")){
					allowFullCrossOver = read.equals("s")? new Boolean(true):new Boolean(false);
				}else{
				}
			}

			xOverNewAge=null;
			while(xOverNewAge==null){
				System.out.println("Cruzar a los que se generaron en la generacion actual?(s/n)");
				String read = bufferRead.readLine();
				if(read.equals("s") || read.equals("n")){
					xOverNewAge = read.equals("s")? new Boolean(true):new Boolean(false);
				}else{
				}
			}

			configuration.addGeneticOperator(new CrossoverOperator(configuration,ratio,allowFullCrossOver,xOverNewAge));
			configUsed.addOperador("CrossOver "+"| ratio: "+ratio+"| fullCross: "+(allowFullCrossOver?"si":"no")+"| overnewAge: "+(xOverNewAge?"si":"no"));

			
			repeat="ble";
			while(!repeat.equals("s") && !repeat.equals("n")){
				System.out.println("Agregar mas operadores?(s/n)");
				repeat = bufferRead.readLine();
			}

		}while(!repeat.equals("n") && configuration.getGeneticOperators().size() > 0);
	}

	private static void agregarSelectores(Configuration configuration) throws InvalidConfigurationException, IOException {
		String repeat = "s";
		do{
			//			limpiarConsola();

			Boolean beforeAfterOperator=null;
			BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));

			System.out.println("<Agregar selectores>");

			while(beforeAfterOperator==null){
				System.out.println("Aplicar el selector antes de los operadores(s/n)?");
				String read = bufferRead.readLine();
				if(read.equals("s") || read.equals("n")){
					beforeAfterOperator = read.equals("s")? new Boolean(true):new Boolean(false);
				}else{
				}
			}

			System.out.println("1:Torneo");
			System.out.println("2:BestChromosomeSelector");
			System.out.println("3:ThresholdSelector");
			System.out.println("4:WeightRouletteSelector");
			int eleccion = 0;
			boolean formatoCorrecto = false;
			while(!formatoCorrecto){
				try{
					eleccion = Integer.parseInt(bufferRead.readLine());
					formatoCorrecto = true;
				}catch(NumberFormatException e){
					System.out.println("Debe ser un numero");
				}
			}

			while(eleccion > 4 || eleccion <= 0){
				System.out.println("Entre 1 y 4");
				eleccion = Integer.parseInt(bufferRead.readLine());
			}

			switch (eleccion) {
			case 1:{
				System.out.println("% de elegir al mejor");
				formatoCorrecto = false;
				Double ratio = null;
				while(!formatoCorrecto){
					try{
						ratio = Double.parseDouble(bufferRead.readLine());
						if(ratio >0 && ratio <= 1){
							formatoCorrecto = true;						
						}else{
							System.out.println("Debe ser un numero decimal menor o igual a 1");	
						}
					}catch(NumberFormatException e){
						System.out.println("Debe ser un numero");
					}
				}
				configuration.addNaturalSelector(new TournamentSelector(configuration, poblacion, ratio), beforeAfterOperator);
				configUsed.addSelector("Torneo con % de elegir al mejor: "+ratio+"| Aplicar antes de los operadores: "+(beforeAfterOperator?"si":"no"));
				break;
			}case 2:{
				System.out.println("% de población considerada");
				formatoCorrecto = false;
				Double ratio = null;
				while(!formatoCorrecto){
					try{
						ratio = Double.parseDouble(bufferRead.readLine());
						if(ratio >0 && ratio <= 1){
							formatoCorrecto = true;						
						}else{
							System.out.println("Debe ser un numero decimal menor o igual a 1");	
						}
					}catch(NumberFormatException e){
						System.out.println("Debe ser un numero");
					}
				}
				configuration.addNaturalSelector(new BestChromosomesSelector(configuration, ratio),beforeAfterOperator);
				configUsed.addSelector("BestChromosomeSelector % de poblacion considerada: "+ratio+"| Aplicar antes de los operadores: "+(beforeAfterOperator?"si":"no"));
				break;
			}case 3:
				System.out.println("% de los mejores cromosomas que tienen que pasar a la proxima generacion");
				formatoCorrecto = false;
				Double ratio = null;
				while(!formatoCorrecto){
					try{
						ratio = Double.parseDouble(bufferRead.readLine());
						if(ratio >0 && ratio <= 1){
							formatoCorrecto = true;						
						}else{
							System.out.println("Debe ser un numero decimal menor o igual a 1");	
						}
					}catch(NumberFormatException e){
						System.out.println("Debe ser un numero");
					}
				}
				configuration.addNaturalSelector(new ThresholdSelector(configuration, ratio), beforeAfterOperator);
				configUsed.addSelector("Threshold % de los mejores cromosomas que tienen que pasar a la siguiente generacion: "+ratio+"| Aplicar antes de los operadores: "+(beforeAfterOperator?"si":"no"));
				break;
			case 4:{
				configuration.addNaturalSelector(new WeightedRouletteSelector(configuration), beforeAfterOperator);
				configUsed.addSelector("Ruleta"+"| Aplicar antes de los operadores: "+(beforeAfterOperator?"si":"no"));
				break;
			}default:
				break;
			}

			repeat="ble";
			while(!repeat.equals("s") && !repeat.equals("n")){
				System.out.println("Agregar mas selectores a los que estan actualmente?(s/n)");
				repeat = bufferRead.readLine();
			}

		}while(!repeat.equals("n") && (configuration.getNaturalSelectors(true).size()>0 || configuration.getNaturalSelectors(false).size()>0));

	}

	public enum MAPAS{
		AmS(0),
		AmN(1),
		EA(2),
		Af(3);

		int n;

		MAPAS(int nroMapa){
			this.n= nroMapa;
		}

		public int getNumero(){
			return n;
		}
	}
}



